﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;

namespace FireMVC
{
    public class Fire : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            FirePHP firePHP = FirePHP.GetInstance();
            firePHP.ClearLog();
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var response = filterContext.HttpContext.Response;
            string label;
            FirePHP firePHP = FirePHP.GetInstance();

            // Request Data
            label = String.Format("Http Request Data {0}", filterContext.HttpContext.Request.Url);
            var headers = filterContext.HttpContext.Request.Headers;

            var tableHeaders = new string[headers.Count + 1][];

            tableHeaders[0] = new string[2] { "Name", "Value" };

            for (int i = 0; i < headers.Count; i++)
            {
                tableHeaders[i+1] = new string[2];
                tableHeaders[i+1][0] = headers.GetKey(i);
                tableHeaders[i+1][1] = headers.Get(i);
            }

            firePHP.Table(label, tableHeaders);

            // ViewData
            label = "ViewData";
            var viewData = filterContext.Controller.ViewData;

            var tableViewData = new string[viewData.Count + 1][];

            tableViewData[0] = new string[2] { "Name", "Value" };

            for (int i = 0; i < viewData.Keys.Count; i++)
            {
                tableViewData[i + 1] = new string[2] { viewData.Keys.ElementAt(i), viewData[viewData.Keys.ElementAt(i)].ToString() };
            }

            firePHP.Table(label, tableViewData);

            // Controller ValueProvider
            label = "Controller ValueProvider";
            var controllerValueProvider = filterContext.Controller.ValueProvider;

            var tableControllerValueProvider = new string[controllerValueProvider.Count + 1][];

            tableControllerValueProvider[0] = new string[2] { "Name", "Value" };

            for (int i = 0; i < controllerValueProvider.Keys.Count; i++)
            {
                tableControllerValueProvider[i + 1] = new string[2] { controllerValueProvider.Keys.ElementAt(i), controllerValueProvider[controllerValueProvider.Keys.ElementAt(i)].AttemptedValue };
            }
            firePHP.Table(label, tableControllerValueProvider);

            // Exception
            if (filterContext.Exception != null)
            {
                firePHP.Exception(filterContext.Exception);
            }

            // Queries
            firePHP.LogSqlQuery();

            // set FirePHP JSON protocol headers
            foreach (var keypair in FirePHP.BaseHeaders())
            {
                response.AppendHeader(keypair.Key, keypair.Value);
            }

            foreach (var keypair in firePHP.LogHeaders())
            {
                response.AppendHeader(keypair.Key, keypair.Value);
            }

            base.OnActionExecuted(filterContext);
        }
    }

    class FirePHPLog
    {
        public string logType;
        public object header; // anonymous type
        public object msg;
    }

    public class FirePHPLinqToSQLLogWriter : System.IO.TextWriter
    {
        private StringBuilder stringBuilder = new StringBuilder();

        public FirePHPLinqToSQLLogWriter()
        {
            FirePHP firePHP = FirePHP.GetInstance();
            firePHP.AddSqlLogWriter(this);
        }

        public override void Write(char[] buffer, int index, int count)
        {
            stringBuilder.Append(new String(buffer, index, count));
        }

        public override void Write(string value)
        {
            stringBuilder.Append(value);
        }

        public override Encoding Encoding
        {
            get { return System.Text.Encoding.Default; }
        }

        public override string ToString()
        {
            return stringBuilder.ToString();
        }
    }

    public class FirePHP
    {
        private static FirePHP _FirePHP = null;
        private static Dictionary<string, string> _BaseHeaders;

        private List<FirePHPLog> _Logs;
        private List<FirePHPLinqToSQLLogWriter> _SqlWriters;

        static FirePHP()
        {
            _BaseHeaders = new Dictionary<string,string>();
            _BaseHeaders.Add("X-Wf-Protocol-1", "http://meta.wildfirehq.org/Protocol/JsonStream/0.2");
            _BaseHeaders.Add("X-Wf-1-Plugin-1", "http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.2.0");
            _BaseHeaders.Add("X-Wf-1-Structure-1", "http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1");
        }

        private FirePHP()
        {
            _Logs = new List<FirePHPLog>();
            _SqlWriters = new List<FirePHPLinqToSQLLogWriter>();
        }

        public static FirePHP GetInstance()
        {
            if (_FirePHP == null)
            {
                _FirePHP = new FirePHP();
            }

            return _FirePHP;
        }

        public void ClearLog()
        {
            _Logs.Clear();
        }

        private void Log(string logType, object msg)
        {
            StackFrame callStack = new StackFrame(2, true);
            FirePHPLog log = new FirePHPLog();

            log.logType = logType;
            log.header = new { Type = logType, File = callStack.GetFileName(), Line = callStack.GetFileLineNumber() };
            log.msg = msg;
            
            _Logs.Add(log);
        }

        public void Debug(object msg)
        {
            Log("DEBUG", msg);
        }

        public void Info(object msg)
        {
            Log("INFO", msg);
        }

        public void Warn(object msg)
        {
            Log("WARN", msg);
        }

        public void Critical(object msg)
        {
            Log("CRITICAL", msg);
        }

        public void Error(object msg)
        {
            Log("ERROR", msg);
        }

        public void Exception(Exception exception)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            StackFrame callStack = new StackFrame(1, true);

            StackTrace stackTrace;
            FirePHPLog log = new FirePHPLog();

            log.logType = "EXCEPTION";
            log.header = new { Type = "EXCEPTION", File = exception.Source, Line = 1 };

            int exceptionCount = 0;
            Exception currentException = exception;

            var traceList = new List<object>();

            while (currentException.InnerException != null) 
            {
                stackTrace = new StackTrace(currentException, true);
                currentException = exception.InnerException;
                exceptionCount++;

                var trace = new { file = currentException.Source, line = currentException.Source, function = currentException.Message, args = new string[0] };
                traceList.Add(trace);
            }

            if (exceptionCount > 0)
            {
                var trace = new object[exceptionCount];
            }
             
            stackTrace = new StackTrace(exception, true);
            
            log.msg = new { 
                Class = "Exception", Message = exception.Message,
                File = stackTrace.GetFrame(0).GetFileName(),
                Line = stackTrace.GetFrame(0).GetFileLineNumber(),
                Type = "throw",
                Trace = traceList.ToArray() 
            };

            _Logs.Add(log);
        }

        public void Table(string label, object[][] table)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            StackFrame callStack = new StackFrame(1, true);
            
            FirePHPLog log = new FirePHPLog();

            log.logType = "TABLE";
            log.header = new { Type = "TABLE", Label = label, File = callStack.GetFileName(), Line = callStack.GetFileLineNumber() };
            log.msg = table;
            
            _Logs.Add(log);
        }

        public void AddSqlLogWriter(FirePHPLinqToSQLLogWriter logWriter)
        {
            _SqlWriters.Add(logWriter);
        }

        public void LogSqlQuery()
        {
            List<string> queries = new List<string>();
            string[][] table; 

            foreach (FirePHPLinqToSQLLogWriter sqlWriter in _SqlWriters)
            {
                foreach (string query in sqlWriter.ToString().Split(new string[1]{"\r\n"}, StringSplitOptions.RemoveEmptyEntries))
                {
                    queries.Add(query);
                }
            }

            table = new string[queries.Count + 1][];

            table[0] = new string[1] { "Query" };

            for (int i = 0; i < queries.Count; i ++)
            {
                table[i + 1] = new string[1] { queries[i] };
            }

            Table("Sql Queries", table);
        }

        public static Dictionary<string, string> BaseHeaders()
        {
            return _BaseHeaders;
        }

        public Dictionary<string, string> LogHeaders()
        {
            Dictionary<string, string> ret = new Dictionary<string,string>();
            JavaScriptSerializer serializer = new JavaScriptSerializer();

            for (int i = 0; i < _Logs.Count; i++)
            {
                string json = serializer.Serialize(new object[2] { _Logs[i].header, _Logs[i].msg });

                StringBuilder builder = new StringBuilder();
                
                foreach (char ch in json)
                {
                    if (CharUnicodeInfo.GetUnicodeCategory(ch) == UnicodeCategory.OtherLetter)
                    {
                        builder.Append(String.Format("\\u{0:x4}", (int)ch));
                    }
                    else
                    {
                        builder.Append(ch);
                    }
                }

                json = builder.ToString();

                ret.Add(String.Format("X-Wf-1-1-1-{0}", (i + 1)), String.Format("{0}|{1}|", json.Length, json));
            }

            ret.Add("X-Wf-1-Index", _Logs.Count.ToString());

            return ret;
        }
    }
}
